package cn.ywyu.classfinal.util;

import lombok.AllArgsConstructor;
import lombok.Data;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 命令行解析器
 */
public class CmdLineParser {

    /**
     * 命令行选项
     */
    private final Map<String, CmdOpts> optionsMap = new HashMap<>();

    private CmdLineParser() {}

    /**
     * 创建命令行加密解析器
     */
    public static CmdLineParser createEncryptParser(String[] args) {
        CmdLineParser encryptParser = new CmdLineParser();
        encryptParser.initOptionsForEncrypt();
        encryptParser.parse(args);
        return encryptParser;
    }

    /**
     * 创建命令行解密解析器
     */
    public static CmdLineParser createDecryptParser(String[] args) {
        CmdLineParser decryptParser = new CmdLineParser();
        decryptParser.initOptionsForDecrypt();
        decryptParser.parse(args);
        return decryptParser;
    }

    /**
     * 初始化加密选项
     */
    private void initOptionsForEncrypt() {
        this.addOption("packages", true, "加密的包名(可为空,多个用\",\"分割)");
        this.addOption("pwd", true, "加密密码");
        this.addOption("code", true, "机器码");
        this.addOption("exclude", true, "排除的类名(可为空,多个用\",\"分割)");
        this.addOption("file", true, "加密的jar/war路径");
        this.addOption("libjars", true, "jar/war lib下的jar(可为空,多个用\",\"分割)");
        this.addOption("classpath", true, "依赖jar包目录(可为空,多个用\",\"分割)");
        this.addOption("cfgfiles", true, "需要加密的配置文件(可为空,多个用\",\"分割)");
        this.addOption("Y", false, "无需确认");
        this.addOption("debug", false, "调试模式");
        this.addOption("C", false, "生成机器码");
    }

    /**
     * 初始化解密选项
     */
    private void initOptionsForDecrypt() {
        this.addOption("pwd", true, "密码");
        this.addOption("pwdname", true, "环境变量密码参数名");
        this.addOption("nopwd", false, "无密码启动");
        this.addOption("debug", false, "调试模式");
        this.addOption("del", true, "读取密码后删除密码");
    }

    /**
     * 添加命令行选项
     *
     * @param opt         选项名称
     * @param hasArg      是否有参数
     * @param description 选项描述
     */
    private void addOption(String opt, boolean hasArg, String description) {
        opt = resolveOption(opt);
        if (!optionsMap.containsKey(opt)) {
            optionsMap.put(opt, new CmdOpts(opt, hasArg, false, description, new ArrayList<>()));
        }
    }

    /**
     * 解析命令行参数
     *
     * @param args 命令行参数
     */
    public void parse(String[] args) {
        if (args == null || args.length == 0) {
            return;
        }
        String argName = null;
        for (String arg : args) {
            arg = arg.trim();
            if (arg.isEmpty()) {
                continue;
            }
            if (arg.startsWith("-")) {
                argName = resolveOption(arg);
                if (!optionsMap.containsKey(argName)) {
                    throw new IllegalArgumentException("Unrecognized option: " + arg);
                }
                optionsMap.get(argName).setUsed(true);
            } else {
                if (optionsMap.containsKey(argName) && optionsMap.get(argName).isNeedValue()) {
                    optionsMap.get(argName).getValues().add(arg);
                }
            }
        }
    }

    /**
     * 获取选项值
     *
     * @param opt 选项名称
     * @return 如果有结果, 返回第一个结果, 否则返回null;
     */
    public String getOptionValue(String opt) {
        return getOptionValue(opt, null);
    }

    /**
     * 获取选项值
     *
     * @param opt 选项名称
     * @param dv  默认值
     * @return 如果有结果, 返回第一个结果, 否则返回dv;
     */
    public String getOptionValue(String opt, String dv) {
        String[] values = getOptionValues(opt);
        return (values == null) ? dv : values[0];
    }

    /**
     * 获取选项值
     *
     * @param opt 选项名称
     * @return 如果有结果, 返回所有结果, 否则返回null;
     */
    public String[] getOptionValues(String opt) {
        List<String> values = optionsMap.get(resolveOption(opt)).getValues();
        return (values == null || values.isEmpty()) ? null : values.toArray(new String[0]);
    }

    /**
     * 判断是否使用选项
     *
     * @param opt 选项名称
     * @return 如果使用, 返回true, 否则返回false;
     */
    public boolean usedOption(String opt) {
        if (optionsMap.containsKey(resolveOption(opt))) {
            return optionsMap.get(resolveOption(opt)).isUsed();
        }
        return false;
    }

    /**
     * 解析选项
     *
     * @param str 选项名称
     * @return 如果str以"-"或"--"开头,则去掉前缀后返回str;
     */
    private static String resolveOption(String str) {
        if (str == null) {
            return null;
        }
        if (str.startsWith("--")) {
            return str.substring(2);
        } else if (str.startsWith("-")) {
            return str.substring(1);
        }
        return str;
    }

    /**
     * 命令行选项实体
     */
    @Data
    @AllArgsConstructor
    public static class CmdOpts {
        /**
         * 选项名称
         */
        private String name;
        /**
         * 是否需要选项值
         */
        private boolean needValue;
        /**
         * 选项是否使用
         */
        private boolean used;
        /**
         * 选项描述
         */
        private String description;
        /**
         * 选项值
         */
        private List<String> values;
    }
}